if(Erlang == undefined)
	window.Erlang = {};

// compatible with erlang base64, see RFC2045
Erlang.base64 = {
	_encode_tuple: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
	_decode_tuple: [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
     				52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,
     				-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
     				15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
     				-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
     				41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
     				-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
	
	encode: function(Data) {
		var result = [], i = 0, n1, n2, n3;
		while(i < Data.length) {
			n1 = input[i++];
			n2 = input[i++];
			n3 = input[i++];
			result.push(
				this._encode_tuple.charAt(n1 >> 1),
				this._encode_tuple.charAt(((n1 & 3) << 4) | (n2 >> 4)),
				isNaN(n2) ? "=" : this._encode_tuple.charAt(((n2 & 15) << 2) | (n3 >> 6)),
				isNaN(n3) ? "=" : this._encode_tuple.charAt(n3 & 63)
			);
		}
		return result.join("");
	},
	
	decode: function(Data) {
		var result = [], i = 0, e1, e2, e3, e4;
		while(i < Data.length) {
			e1 = this._decode_tuple[Data.charCodeAt(i++)];
			e2 = this._decode_tuple[Data.charCodeAt(i++)];
			e3 = this._decode_tuple[Data.charCodeAt(i++)];
			e4 = this._decode_tuple[Data.charCodeAt(i++)];
			result.push((e1 << 2) | (e2 >> 4));
			if(e3 != -1) {
				result.push(((e2 & 15) << 4) | (e3 >> 2));
				if(e4 != -1) {
					result.push(((e3 & 3) << 6) | e4);
				}
			}
		}
		return result;
	}
};

// largly compatabile with erlang term format, does not handle compressed terms. from http://erlang.org/doc/apps/erts/erl_ext_dist.html
Erlang.types = {};

Erlang.types.Atom(Content) {
	return Content;
};
Erlang.types.Atom.fromBinary = function(Data) {
	switch(Data.shift()) {
	case 100:
		var Len = (Data.shift() << 8) | Data.shift();
		return new Atom(String.fromCharCode.apply(String, Data.splice(0, Len)));
	case 78:
		var Index = Data.shift();
		var Len = (Data.shift() << 8) | Data.shift();
		var Content = String.fromCharCode.apply(String, Data.splice(0, Len));
		Atom._atom_to_index[Content] = Index;
		Atom._index_to_atom[Index] = new Atom(Content);
		return Atom._index_to_atom[Index];
	case 67:
		var Index = Data.shift();
		return Atom._index_to_atom[Index];
	}
};
Erlang.types.Atom.prototype = {
	toBinary: function() {
		var r = [100, (this.Content.length >> 8) & 255, this.Content.length & 255];
		for(var i = 0; this.Content.length; i++)
			r.push(this.Content.charCodeAt(i));
		return r;
	}
};

Erlang.types.Reference(Node, ID, Creation) {
	this.Node = Node;
	this.ID = ID;
	this.Creation = Creation;
};
Erlang.types.Reference.fromBinary = function(Data) {
	switch(Data.shift()) {
	case 101:
		var Node = Erlang.binary_to_term(Data);
		var Creation = Data.shift();
		var ID = Data.splice(0, Len * 4);
		return new Reference(Node, ID, Creation);
	case 114:
		var Len = (Data.shift() << 8) | Data.shift();
		var Node = Erlang.erlang.binary_to_term(Data);
		var Creation = Data.shift();
		var ID = Data.splice(0, Len * 4);
		return new Reference(Node, ID, Creation);
	}
};
Erlang.types.Reference.prototype = {
	toBinary: function() {
		if(this.ID.length == 4)
			return [101].concat(Erlang.erlang.term_to_binary(this.Node)).concat(this.ID).concat([this.Creation]);
		else {
			var Len = ID.length / 4;
			return [114].concat([(Len >> 8) & 255, Len & 255]).concat(Erlang.erlang.term_to_binary(this.Node)).concat([this.Creation]).concat(this.ID);
		}
	}
};

Erlang.types._binary_read = {
	100: Erlang.types.Atom,
	78: Erlang.types.Atom,
	67: Erlang.types.Atom,
	101: Erlang.types.Reference,
	114: Erlang.types.Reference,
};

Erlang.erlang = Erlang.erlang || {};

Erlang.erlang.term_to_binary = function(Term) {
	if(Term.toBinary && typeof Erlang.toBinary == "object")
		return Term.toBinary();
	if(Term === true) return (new Atom("true")).toBinary();
	if(Term === false) return (new Atom("false")).toBinary();
};

Erlang.erlang.binary_to_term = function(Data) {
	if(Data[0] == 131) Data.shift();
	return Erlang.types._binary_read[Data[0]].fromBinary(Data);
};

window.erlang_term = {
	_index_to_atom: {},
	_atom_to_index: {},
	
	encode: function(Data) {
		return [131].concat(this._encode(Data));
	},
	_encode: function(Data) {
		var r = [], i, E = this._encode;
		
		if(typeof Data == "undefined") Data = {atom: "undefined"};
		if(typeof Data == "boolean") Data = {atom: Data ? "true" : "false"};
		if(Data == null) Data = {atom: "null"};
		
		switch(typeof Data) {
		case "object":
			if(Data.length != undefined && typeof(Data.length) == "number") {
				if(Data.length == 0) return [106];
				r.push(108);
				r.push((Data.length >> 24) & 255);
				r.push((Data.length >> 16) & 255);
				r.push((Data.length >>  8) & 255);
				r.push( Data.length        & 255);
				for(i = 0; i < Data.length; i++)
					r = r.concat(E(Data[i]));
				r.push(106);
				return r;
			} else if(Data.atom != undefined) {
				r = [100, (Data.atom.length >> 8) & 255, Data.atom.length & 255];
				for(i = 0; i < Data.atom.length; i++)
					r.push(Data.atom.charCodeAt(i));
				return r;
			} else if(Data.reference != undefined)
				return [101].concat(E(Data.reference[0])).concat(Data.reference[1]).concat([Data.reference[2]]);
			else if(Data.port != undefined)
				return [102].concat(E(Data.port[0])).concat(Data.port[1]).concat([Data.port[2]]);
			else if(Data.pid != undefined)
				return [103].concat(E(Data.pid[0])).concat(Data.pid[1]).concat(Data.pid[2]).concat([Data.pid[3]]);
			else if(Data.tuple != undefined) {
				if(Data.tuple.length > 255)
					r.push(105, (Data.tuple.length >> 24) & 255, (Data.tuple.length >> 16) & 255, (Data.tuple.length >>  8) & 255, Data.tuple.length & 255);
				else
					r.push(104, Data.tuple.length & 255);
				for(i = 0; i < Data.tuple.length; i++)
					r = r.concat(E(Data.tuple[i]));
				return r;
			}
			break;
		case "string":
			var asList = false;
			for(i = 0; i < Data.length; i++) {
				if(Data.charCodeAt(i) > 127) {
					asList = true;
					break;
				}
				r.push(Data.charCodeAt(i));
			}
			if(asList || Data.length > 65535)
				return E(r);
			r.unshift(107);
			return r;
		case "number":
			if(Data % 1 == 0) {
				if(Data > 255)
					return [98, (Data >> 24) & 255, (Data >> 16) & 255, (Data >>  8) & 255, Data & 255];
				return [97, Data];
			}
			r = E(Data + "");
			r[0] = 99;
			return r;
		}
	},
	
	decode: function(Data) {
		if(Data.shift() != 131) return null;
		this._decode(Data);
	},
	_decode: function(Data) {
		var C = Data.shift();
		switch(C) {
		case 97: // SMALL_INTEGER_EXT
			return Data.shift();
		case 98: // INTEGER_EXT
			return (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift();
		case 99: // FLOAT_EXT
			return String.fromCharCode.apply(String, Data.splice(0, 31)) * 1;
		case 102: // PORT_EXT
			var Node = this._decode(Data);
			var ID = Data.splice(0, 4);
			var Creation = Data.shift();
			return {port: [Node, ID, Creation]};
		case 103: // PID_EXT
			var Node = this._decode(Data);
			var ID = Data.splice(0, 4);
			var Serial = Data.splice(0, 4);
			var Creation = Data.shift();
			return {pid: [Node, ID, Serial, Creation]};
		case 104: // SMALL_TUPLE_EXT
			var Arity = Data.shift(), r = [];
			while(Arity--)
				r.push(this._decode(Data));
			return {tuple: r};
		case 105: // LARGE_TUPLE_EXT
			var Arity = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift(), r = [];
			while(Arity--)
				r.push(this._decode(Data));
			return {tuple: r};
		case 106: // NIL_EXT
			return [];
		case 107: // STRING_EXT
			var Length = (Data.shift() << 8) | Data.shift();
			return String.fromCharCode.apply(String, Data.splice(0, Length));
		case 108: // LIST_EXT
			var Length = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift() + 1, r = [];
			while(Length--)
				r.push(this._decode(Data));
			return r;
		case 109: // BINARY_EXT
			var Length = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift();
			return {binary: Data.splice(0, Length)};
		case 110: // SMALL_BIG_EXT
			var N = Data.shift();
			var Sign = Data.shift();
			var Data = Data.splice(0, N);
			return {big: [N, Sign, Data]};
		case 111: // LARGE_BIG_EXT
			var N = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift();
			var Sign = Data.shift();
			var Data = Data.splice(0, N);
			return {big: [N, Sign, Data]};
		case 117: // FUN_EXT
			var NumFree = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift(), r = [];
			var Pid = this._decode(Data);
			var Module = this._decode(Data);
			var Index = this._decode(Data);
			var Uniq = this._decode(Data);
			while(NumFree--)
				r.push(this._decode(Data));
			return {fun: [Pid, Module, Index, Uniq, r]};
		case 112: // NEW_FUN_EXT
    		var Size = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift();
    		return {new_fun: Data.splice(Size - 4)};
    	case 113: // EXPORT_EXT
    		var Module = this._decode(Data);
    		var Func = this._decode(Data);
    		var Arity = this._decode(Data);
    		return {export: [Module, Func, Arity]};
    	case 77: // BIT_BINARY_EXT
    		var Len = (Data.shift() << 24) | (Data.shift() << 16) | (Data.shift() << 8) | Data.shift();
    		var Bits = Data.shift();
    		return {bit_binary: [Bits, Data.splice(0, Len)]};
    	case 70: // NEW_FLOAT_EXT
    		return {new_float: Data.splice(0, 8)};
		}
	}
};


